<template>
  <view
    :id="id"
    class="tn-lazy-load"
    :style="lazyLoadStyle"
  >
    <view class="tn-lazy-load__wrap" @tap.stop="handleImgClick">
      <image
        v-if="!loadError"
        class="tn-lazy-load__image"
        :src="showImage ? src : loadingImg"
        :mode="imgMode"
        @load="handleImgLoaded"
        @error="handleImgError"
      ></image>
      <image
        v-else
        class="tn-lazy-load__image tn-lazy-load__image--error"
        :src="loadErrorImg"
        :mode="imgMode"
        @load="handleErrorImgLoaded"
      ></image>
    </view>
  </view>
</template>

<script lang="ts" setup>
import { lazyLoadProps, lazyLoadEmits, LazyLoadClickEvent } from '../../../types/tn-lazy-load'
import { LAZY_LOAD_BOTTOM_EVENT } from '../../../constants'
import { computed, ref, watch, CSSProperties, getCurrentInstance, ComponentInternalInstance, onMounted, nextTick } from 'vue'
import { handleValueWithUnit, uuid, getRectInfo } from '../../../utils'
import useObserver from '../lib/useObserver'

const props = defineProps(lazyLoadProps)
const emits = defineEmits(lazyLoadEmits)

// id
const id: string = uuid()
const instance = getCurrentInstance() as ComponentInternalInstance

// 组件透明度(让组件有动画效果)
let opacity = ref<number>(1)
// lazyLoad样式
const lazyLoadStyle = computed<CSSProperties>(() => {
  let style: CSSProperties = {}
  if (props?.animation && props?.duration) {
    style.transition = `opacity ${props.duration}ms ${props?.timeFunction || 'ease-in-out'}`
  }
  if (props?.height) {
    style.height = handleValueWithUnit(props.height)
  }
  style.opacity = opacity.value
  return Object.assign(style, props?.customStyle || {})
})

// 标记图片是否显示
let showImage = ref<boolean>(false)
watch(showImage, (val: boolean) => {
  // 先设置opacity位0然后一段时间后再设置为1
  opacity.value = 0
  setTimeout(() => opacity.value = 1, 50);
})
// 图片加载状态
let loadStatus: LazyLoadClickEvent['status'] = 'waiting'
let loadError = ref<boolean>(false)

// 由于H5下无法获取到父组件的copmonent, 所以需要传递该参数(等待uniapp官方修复)
// #ifdef H5
// #endif
const { connectObserver, disconnectObserver } = useObserver(instance)

// 初始化组件节点信息
let initObserverCount: number = 0
const initObserver = async () => {
  // 查看节点是否存在
  const rect = await getRectInfo(`#${id}`, instance)
  if (!rect) {
    if (initObserverCount > 10) {
      return console.error('[tn-lazy-load]初始化监听器失败')
    }
    setTimeout(() => {
      initObserver()
      initObserverCount++
    }, 50);
    return
  }
  // console.log(rect);
  initObserverCount = 0

  // 监听组件节点交叉状态
  disconnectObserver()
  const bottomThreshold = typeof props.threshold !== 'undefined' ? Number(props.threshold) : 450
  const bottomThresholdValue = bottomThreshold < 0 ? -uni.upx2px(Math.abs(bottomThreshold)) : uni.upx2px(Math.abs(bottomThreshold))
  connectObserver(`#${id}`, bottomThresholdValue, (res: UniApp.ObserveResult) => {
    if (res.intersectionRatio > 0) {
      // 开始显示图片
      showImage.value = true
      // 图片已经加载去除监, 减少资源浪费
      disconnectObserver()
    }
  })

  // 创建全局监听事件
  // 用于监听当页面触发底部的时候进行图片加载，防止图片没有加载出来
  uni.$once(LAZY_LOAD_BOTTOM_EVENT, () => {
    if (!showImage.value) showImage.value = true
  })
}

// 重置标识信息
const resetFlag = () => {
  loadError.value = false
  loadStatus = 'waiting'
}
watch(() => props.src, () => {
  // 图片修改后重置标识
  resetFlag()
})

// 处理图片加载完毕事件
const handleImgLoaded = () => {
  if (loadStatus === 'waiting') {
    // 占位图加载完毕设置为正在加载中
    loadStatus = 'loading'
  } else if (loadStatus === 'loading') {
    // 从加载中变为加载完毕
    loadStatus = 'real'
    emits('loaded', props.index)
  }
}
// 处理加载出错图片加载完毕事件
const handleErrorImgLoaded = () => {
  if (!loadError) return
  loadStatus = 'error'
  emits('error', props.index)
}
// 图片加载失败
const handleImgError = () => {
  console.log('error');
  loadError.value = true
}

// 图片点击事件
const handleImgClick = () => {
  emits('click', {
    index: props.index,
    status: loadStatus
  })
}

onMounted(() => {
  nextTick(() => {
    setTimeout(() => {
      initObserver()
    }, 50);
  })
  
})
</script>

<script lang="ts">
export default {
  options: {
    // 在微信小程序中将组件节点渲染为虚拟节点，更加接近Vue组件的表现(不会出现shadow节点下再去创建元素)
    virtualHost: true
  }
}
</script>

<style lang="scss" scoped>

.tn-lazy-load {
  position: relative;
  background-color: #F4F4F4;
  overflow: hidden;
  border-radius: inherit;

  &__wrap {
    width: 100%;
    height: 100%;
    border-radius: inherit;
  }

  &__image {
    width: 100%;
    height: 100%;
    border-radius: inherit;
    // 解决父容器会多出3px的问题
    display: block;
    width: 100%;
    // 骗系统开启硬件加速
    transform: transition3d(0, 0, 0);
    // 防止图片加载“闪一下”
    will-change: transform;
  }
}
</style>
